package tld.test.menu.challenges
{
	import flash.display.DisplayObject;
	import flash.display.Graphics;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.filters.GlowFilter;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;
	import flash.utils.Dictionary;
	
	import tld.test.interpreter.IContext;
	import tld.test.menu.Challenge;
	import tld.test.menu.Console;
	import tld.test.menu.IChallenge;
	import tld.test.menu.challenges.ChallengeEvent;
	
	[Event(name="win", type="tld.test.menu.challenges.ChallengeEvent")]
	
	[Event(name="loose", type="tld.test.menu.challenges.ChallengeEvent")]
	
	public class AshesToAshes extends Sprite implements IChallenge
	{
		private const _challenge:Challenge = new Challenge();
		
		private const _filter:Array = [new GlowFilter()];
		
		private const _format:TextFormat = 
			new TextFormat("_sans", 18, 0xFFFFFF);
		
		private const _kinds:Object =
		{
			"circle" : drawCircle,
			"triangle" : drawTriangle,
			"square" : drawSquare
		}
		
		private var _sources:Array;
		
		private var _targets:Array;
		
		private var _mousePressed:Boolean;
		
		private const _sourceHash:Dictionary = new Dictionary(true);
		
		private const _targetHash:Dictionary = new Dictionary(true);
		
		private const _coordinates:Point = new Point();
		
		private var _currentTarget:Sprite;
		
		private var _console:Console;
		
		private const _names:Object =
		{
			"reset" : reset,
			"move" : move,
			"drop" : drop,
			"pick" : pick
		}
		
		public function AshesToAshes()
		{
			super();
			for (var name:String in this._names)
				this._challenge.put(name, this._names[name]);
			// We could actually do this upon adding / removing 
			// to / from stage, but for shortness this will do for now.
			super.addEventListener(
				Event.ENTER_FRAME, this.enterFrameHandler);
			super.addEventListener(
				MouseEvent.MOUSE_UP, this.mouseUpHandler);
			super.addEventListener(
				MouseEvent.MOUSE_DOWN, this.mouseDownHandler);
		}
		
		public function setConsole(console:Console):void
		{
			this._console = console;
		}
		
		public function challenge():IContext
		{
			return this._challenge;
		}
		
		public function help():String
		{
			return <![CDATA[;;; This is AshesToAshes program

; You are tested on precision skills:
; your mission is to find similar geometrical shapes and collide them.
;   (pick #) - # - is the number as appears on the shape.
;   (move x y) - x and y are the coordinates (in pixels). Call this
;                function to move the shape around.
;   (drop) - call this function to drop the target you last moved.
;
; Advanced users can try `reset' option. `reset' requires two arguments
; of type Array each containing kinds of shapes you  want to create:
; (reset '("circle" "circle" "triangle")
;        '("triangle" "triangle" "rectange"))
;  will create 2 circular sources and two triangular targes.

; call (unload) to unload this challenge.]]>.toString();
		}
		
		public function defaults():String
		{
			return <![CDATA[
			(reset '("circle" "triangle" "square") 
				'("circle" "triangle" "square"))]]>.toString();
		}
		
		public function drop():void
		{
			var picked:String;
			var dropTargets:int;
			var found:String = ChallengeEvent.LOOSE;
			
			if (this._currentTarget)
			{
				picked = this._targetHash[this._currentTarget];

				for (var source:Object in this._sourceHash)
				{
					if ((source as DisplayObject).hitTestObject(
							this._currentTarget) && 
						this._sourceHash[source] == picked)
					{
						found = ChallengeEvent.WIN;
						break;
					}
				}
				this._coordinates.x = this._coordinates.y = 0;
				super.dispatchEvent(new ChallengeEvent(found));
				this._currentTarget.filters = [];
				this._currentTarget = null;
				this._mousePressed = false;
			}
		}
		
		public function move(whereX:int, whereY:int):void
		{
			if (this._currentTarget)
			{
				this._coordinates.x = whereX;
				this._coordinates.y = whereY;
				this._currentTarget.x = whereX;
				this._currentTarget.y = whereY;
			}
		}
		
		public function pick(index:int):void
		{
			if (this._currentTarget) this.drop();
			this._currentTarget = 
				this.findByKey(this._targetHash, index);
			super.addChild(this._currentTarget);
			this._currentTarget.filters = this._filter;
		}
		
		public function reset(sources:Array, targets:Array):void
		{
			this._sources = sources.concat();
			this._targets = targets.concat();
			this.draw();
		}
		
		private function findByKey(hash:Dictionary, key:int):Sprite
		{
			var result:Sprite;
			
			for (var member:Object in hash)
			{
				result = member as Sprite;
				if (int((result.getChildAt(0) as TextField).text) == key)
					break;
				result = null;
			}
			return result;
		}
		
		private function draw():void
		{
			var sprite:Sprite;
			var kind:String;
			var index:int;
			
			while (super.numChildren) super.removeChildAt(0);
			
			for each (kind in this._sources)
				this._sourceHash[this.drawShape(kind, index)] = kind;
			for each (kind in this._targets)
			{
				sprite = this.drawShape(kind, index++, true);
				this._targetHash[sprite] = kind;
			}
		}
		
		private function drawShape(kind:String, 
			index:int, target:Boolean = false):Sprite
		{
			return this._kinds[kind](index, target);
		}
		
		private function drawCircle(index:int, 
			target:Boolean = false):Sprite
		{
			var sprite:Sprite = new Sprite();
			var canvas:Graphics = sprite.graphics;
			
			canvas.beginFill(Math.random() * 0xFFFFFF);
			canvas.drawCircle(0, 0, 30 + Math.random() * 70);
			sprite.mouseChildren = false;
			// These should come as settings, later
			sprite.x = Math.random() * 300;
			sprite.y = Math.random() * 200;
			if (target) this.drawText(sprite, index);
			return super.addChild(sprite) as Sprite;
		}
		
		private function drawTriangle(index:int, 
			target:Boolean = false):Sprite
		{
			var sprite:Sprite = new Sprite();
			var canvas:Graphics = sprite.graphics;
			var radius:int = 30 + Math.random() * 70;
			var angle:Number = Math.PI * Math.random() * 2;
			var degree120:Number = Math.PI * 2 / 3;
			var i:int = 3;
			
			canvas.beginFill(Math.random() * 0xFFFFFF);
			canvas.moveTo(radius * Math.sin(angle + degree120), 
				radius * Math.cos(angle + degree120));
			angle += degree120;
			while (i--)
			{
				canvas.lineTo(radius * Math.sin(angle + degree120), 
					radius * Math.cos(angle + degree120));
				angle += degree120;
			}
			sprite.mouseChildren = false;
			// These should come as settings, later
			sprite.x = Math.random() * 300;
			sprite.y = Math.random() * 200;
			if (target) this.drawText(sprite, index);
			return super.addChild(sprite) as Sprite;
		}
		
		private function drawSquare(index:int, 
			target:Boolean = false):Sprite
		{
			var sprite:Sprite = new Sprite();
			var canvas:Graphics = sprite.graphics;
			var random:int = (30 + Math.random() * 70) / 2;
			
			canvas.beginFill(Math.random() * 0xFFFFFF);
			canvas.drawRect(-random, -random, 2 * random, 2 * random);
			sprite.mouseChildren = false;
			// These should come as settings, later
			sprite.x = Math.random() * 300;
			sprite.y = Math.random() * 200;
			if (target) this.drawText(sprite, index);
			return super.addChild(sprite) as Sprite;
		}
		
		private function drawText(where:Sprite, index:int):void
		{
			var text:TextField = new TextField();
			text.autoSize = TextFieldAutoSize.LEFT;
			text.defaultTextFormat = this._format;
			text.text = index.toString();
			text.x = -(text.width >> 1);
			text.y = -(text.height >> 1);
			where.addChild(text);
		}
		
		private function enterFrameHandler(event:Event):void
		{
			if (this._currentTarget && this._mousePressed)
			{
				this._currentTarget.x = super.mouseX;
				this._currentTarget.y = super.mouseY;
			}
			if (this._currentTarget && 
				(int(this._coordinates.x) != int(this._currentTarget.x) || 
					int(this._coordinates.y) != int(this._currentTarget.y)))
			{
				if (this._console)
					this._console.run("(move " + 
						super.mouseX + " " + super.mouseY + ")");
				else this.move(super.mouseX, super.mouseY);
			}
		}
		
		private function mouseDownHandler(event:MouseEvent):void
		{
			var sprite:Sprite = event.target as Sprite;
			var field:TextField;
			
			if (sprite in this._targetHash)
			{
				field = sprite.getChildAt(0) as TextField;
				this._mousePressed = true;
				if (this._console)
					this._console.run("(pick " + int(field.text) + ")");
				else this.pick(int(field.text));
			}
		}
		
		private function mouseUpHandler(event:MouseEvent):void
		{
			if (this._currentTarget)
			{
				if (this._console) this._console.run("(drop)");
				else this.drop();
			}
			this._mousePressed = false;
		}
	}
}